package bancosys.tec.rtm.impl.diagnostician;

import java.util.concurrent.TimeUnit;

import jmine.tec.utils.debug.impl.PartialTime;
import bancosys.tec.rtm.diagnostician.AbstractDiagnostician;
import bancosys.tec.rtm.diagnostician.DiagnosisBean;

/**
 * Diagnostician abstrato para que insere medicao de tempo. Subclasses devem sobrescrever {@link #performOperation(DiagnosisBean)} ou
 * {@link #performOperationCount(DiagnosisBean)} se a frequencia eh desejavel
 * 
 * @author takeshi
 */
public abstract class AbstractPerformanceDiagnostician extends AbstractDiagnostician {
    private static final int WARMUP_ITERATIONS = 5500;

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean fillDiagnosisBean(DiagnosisBean bean) throws Exception {
        warmup();
        PartialTime partial = new PartialTime();
        long amount = performOperationCount(bean);
        PartialTime now = new PartialTime();
        final long duration = now.getSystemTime() - partial.getSystemTime();
        bean.addDescription("real time (ms): " + TimeUnit.NANOSECONDS.toMillis(duration));
        bean.addDescription("user time (ms): " + TimeUnit.NANOSECONDS.toMillis(now.getUserTime() - partial.getUserTime()));
        bean.addDescription("cpu time (ms): " + TimeUnit.NANOSECONDS.toMillis(now.getCpuTime() - partial.getCpuTime()));
        if (amount > 0) {
            bean.addDescription("Sample size: " + amount);
            double ts = (double) duration / amount;
            bean.addDescription("real time (ms) / iteration: " + ts);
        }
        return true;
    }

    /**
     * @param bean {@link DiagnosisBean}
     * @return long
     * @throws Exception e
     */
    protected long performOperationCount(DiagnosisBean bean) throws Exception {
        this.performOperation(bean);
        return 0;
    }

    /**
     * Metodo que vai ter o tempo medido de fato
     * 
     * @param bean {@link DiagnosisBean} o bean a ser preenchido
     * @throws Exception e
     */
    protected void performOperation(DiagnosisBean bean) throws Exception {
        throw new AbstractMethodError("" + this.getClass() + ".performOperation");
    }

    /**
     * Faz o warmup do metodo para tentar diminuir a interferencia do JIT no tempo
     * 
     * @throws Exception e
     */
    protected void warmup() throws Exception {
        for (int i = 0; i < WARMUP_ITERATIONS; i++) { // jvm warmup phase
            this.warmupStep();
        }
    }

    /**
     * Faz um passo do 'warmup'
     * 
     * @throws Exception e
     */
    protected void warmupStep() throws Exception {
    }

}
